﻿using System;
using System.Collections.Generic;
using System.Text;

namespace TrackType
{
    /// <summary>
    /// Represents album header, which contains album cathegory, tracktype album code, artist name, album name etc
    /// </summary>
    public class AlbumHeader
    {
        protected string _cathegory;
        protected string _album_code;
        protected string _artist;
        protected string _album_name;

        /// <summary>
        /// Gets cathegory name 
        /// </summary>
        public string CathegoryName
        {
            get
            {
                return _cathegory;
            }
        }
        /// <summary>
        /// Gets album header's unique code
        /// </summary>
        public string AlbumCode
        {
            get
            {
                return _album_code;
            }
        }
        /// <summary>
        /// Gets artist name
        /// </summary>
        public string ArtistName
        {
            get
            {
                return _artist;
            }
        }
        /// <summary>
        /// Gets album name
        /// </summary>
        public string AlbumName
        {
            get
            {
                return _album_name;
            }
        }

        protected AlbumHeader() { }

        /// <summary>
        /// Attempts to parse header string and create new instance of AlbumHeader
        /// </summary>
        /// <param name="headerStr"></param>
        /// <returns>New instance of Album header if succeded, null-reference otherwise</returns>
        public static AlbumHeader Parse(string headerStr)
        {
            AlbumHeader result = null;

            if (headerStr != null)
            {
                string[] words = headerStr.Split(' ');

                if (words != null && words.Length > 4)
                {
                    try
                    {
                        string separator = "/";

                        result = new AlbumHeader();
                        result._cathegory = words[0];
                        result._album_code = words[1];

                        int index = 2;

                        while (words[index] != separator && index < words.Length)
                        {
                            result._artist += words[index];
                            result._artist += ' ';
                            ++index;
                        }
                        result._artist = result._artist.TrimEnd(' ');

                        ++index;

                        while (index < words.Length)
                        {
                            result._album_name += words[index];
                            result._album_name += ' ';
                            ++index;
                        }
                        result._album_name = result._album_name.TrimEnd(' ');
                    }
                    catch
                    {
                        result = null;
                    }
                }

            }

            return result;
        }
    }

    /// <summary>
    /// Represents album data model
    /// </summary>
    public class Album
    {
        /// <summary>
        /// Number of frames in 1 second
        /// </summary>
        const byte FRAME_LEN_MULTIPLIER = 75; // 1 frame == 1/75 sec

        int _year;
        long _disc_len_seconds;
        long _disc_len_frames;
        Track[] _tracks;
        string _genre;
        string _discid;
        string _name;
        string _artist_name;

        /// <summary>
        /// Gets album release year
        /// </summary>
        public int Year
        {
            get
            {
                return _year;
            }
        }
        /// <summary>
        /// Gets total album length in seconds
        /// </summary>
        public long DiscLength
        {
            get
            {
                return _disc_len_seconds;
            }
        }
        /// <summary>
        /// Gets all album tracks. (Order is preserved)
        /// </summary>
        public Track[] Tracks
        {
            get
            {
                return _tracks;
            }
        }
        /// <summary>
        /// Gets album genre
        /// </summary>
        public string Genre
        {
            get
            {
                return _genre;
            }
        }
        /// <summary>
        /// Returns album identifier
        /// </summary>
        public string DiscID
        {
            get
            {
                return _discid;
            }
        }
        /// <summary>
        /// Gets album name
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
        }
        /// <summary>
        /// Gets artist name
        /// </summary>
        public string Artist
        {
            get
            {
                return _artist_name;
            }
        }

        protected Album() { }

        /// <summary>
        /// Attempts to parse list of text lines and create new Album instance
        /// </summary>
        /// <param name="lines"></param>
        /// <returns>New Album instance if succeded, null-reference otherwise</returns>
        public static Album Parse(IList<string> lines)
        {
            Album result = null;

            try
            {
                if (lines != null)
                {
                    Album al = new Album();
                    if (al.ParseLines(lines))
                    {
                        result = al;
                    }
                }
            }
            catch { }

            return result;
        }

        /// <summary>
        /// Checks whether album has given track
        /// </summary>
        /// <param name="track"></param>
        /// <returns>True if album contains given track, false otherwise</returns>
        public bool ContainsTrack(Track track)
        {
            bool result = false;

            if (_tracks != null)
            {
                for (int i = 0; i < _tracks.Length; ++i)
                {
                    if (_tracks[i] == track)
                    {
                        result = true;
                        break;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Creates AlbumParser and attempts to parse album instance from text lines
        /// </summary>
        /// <param name="lines"></param>
        /// <returns>True if succeded, false otherwise</returns>
        protected bool ParseLines(IList<string> lines)
        {
            bool result = false;

            try
            {
                AlbumParser parser = new AlbumParser(lines);
                if (parser.Parse())
                {
                    _discid = parser.DiscID;
                    _year = parser.Year;
                    _disc_len_seconds = parser.DiscLength;
                    _disc_len_frames = _disc_len_seconds * FRAME_LEN_MULTIPLIER;
                    _genre = parser.Genre;
                    _name = parser.AlbumName;
                    _artist_name = parser.ArtistName;

                    _tracks = CreateTracks(parser.TracksNames, parser.TrackFrames, _disc_len_frames);

                    if (_tracks != null)
                    {
                        result = true;
                    }
                }

            }
            catch { }

            return result;
        }
        /// <summary>
        /// Attempts to create tracks for album using track names, offsets and disk length
        /// </summary>
        /// <param name="names">Array of track names</param>
        /// <param name="frameOffsets">Array of frame offsets</param>
        /// <param name="discFrameLength">Disk length in frames</param>
        /// <returns>Array of tracks if succeded, null-reference otherwise</returns>
        protected Track[] CreateTracks(string[] names, long[] frameOffsets, long discFrameLength)
        {
            Track[] result = null;

            try
            {
                int tracksCount = names.Length;

                if (tracksCount == frameOffsets.Length)
                {
                    float[] tracks_length = CalculateTracksLength(frameOffsets, discFrameLength);

                    if (tracks_length != null && tracksCount == tracks_length.Length)
                    {
                        result = new Track[tracksCount];

                        for (int i = 0; i < tracksCount; ++i)
                        {
                            result[i] = new Track(i, names[i], tracks_length[i], frameOffsets[i]);
                        }
                    }
                }
            }
            catch
            {
                result = null;
            }

            return result;
        }
        /// <summary>
        /// Attempts to calculate track length in seconds using track frame offsets and disk frame length
        /// </summary>
        /// <param name="frameOffsets">Tracks' frame offsets</param>
        /// <param name="discFrameLength">Disk length in frames</param>
        /// <returns>Tracks' lengths in seconds if succede, null-reference otherwise</returns>
        protected float[] CalculateTracksLength(long[] frameOffsets, long discFrameLength)
        {
            float[] result = null;

            try
            {
                int high = frameOffsets.Length - 1;
                result = new float[frameOffsets.Length];

                for (int i = 0; i < high; ++i)
                {
                    result[i] = ((float)(frameOffsets[i + 1] - frameOffsets[i]) / FRAME_LEN_MULTIPLIER);
                }

                //calculate last track length
                result[high] = ((float)(discFrameLength - frameOffsets[high]) / FRAME_LEN_MULTIPLIER);
            }
            catch
            {
                result = null;
            }

            return result;
        }
    }

    /// <summary>
    /// Represents album's track data model
    /// </summary>
    public class Track
    {
        int _index;
        string _name;
        float _duration;
        long _offset;

        /// <summary>
        /// Gets zero-base index of track in album
        /// </summary>
        public int Index
        {
            get
            {
                return _index;
            }
        }
        /// <summary>
        /// Gets track name
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
        }
        /// <summary>
        /// Gets track duration in seconds
        /// </summary>
        public float Duration
        {
            get
            {
                return _duration;
            }
        }
        /// <summary>
        /// Gets frame-offset of track (TOC parameter)
        /// </summary>
        public long FrameOffset
        {
            get
            {
                return _offset;
            }
        }

        protected Track() { }

        /// <summary>
        /// Creates new instance of Track
        /// </summary>
        /// <param name="Index">Index of track in album (e.g. position in CUE-sheet)</param>
        /// <param name="Name">Track name</param>
        /// <param name="Duration">Duration in seconds</param>
        /// <param name="FrameOffset">Frameoffset in CUE-sheet</param>
        public Track(int Index, string Name, float Duration, long FrameOffset)
        {
            this._index = Index;
            this._name = Name;
            this._duration = Duration;
            this._offset = FrameOffset;
        }
    }
}
